package com.app.util;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class BCECUtil {
    private static Logger logger = LoggerFactory.getLogger(BCECUtil.class);
    private static final String ALGO_NAME_EC = "EC";
    private static final String PEM_STRING_PUBLIC = "PUBLIC KEY";
    private static final String PEM_STRING_ECPRIVATEKEY = "EC PRIVATE KEY";


    private BCECUtil() {
        throw new IllegalStateException("Utility class");
    }

    static {
        try {
            Security.addProvider(new BouncyCastleProvider());
        } catch (Exception e) {
            logger.error("加载失败",e);
        }
    }

    public static AsymmetricCipherKeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random) {
        ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters, random);
        ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
        keyGen.init(keyGenerationParams);
        return keyGen.generateKeyPair();
    }

    public static int getCurveLength(ECKeyParameters ecKey) {
        return getCurveLength(ecKey.getParameters());
    }

    public static int getCurveLength(ECDomainParameters domainParams) {
        return (domainParams.getCurve().getFieldSize() + 7) / 8;
    }

    public static ECPrivateKeyParameters createEcPrivateKey(BigInteger d, ECDomainParameters domainParameters) {
        return new ECPrivateKeyParameters(d, domainParameters);
    }

    public static ECPublicKeyParameters createEcPublicKey(BigInteger x, BigInteger y, ECCurve curve, ECDomainParameters domainParameters) {
        byte[] xBytes = x.toByteArray();
        byte[] yBytes = y.toByteArray();
        return createEcPublicKey(xBytes, yBytes, curve, domainParameters);
    }

    public static ECPublicKeyParameters createEcPublicKey(String xHex, String yHex, ECCurve curve, ECDomainParameters domainParameters) {
        byte[] xBytes = ByteUtils.fromHexString(xHex);
        byte[] yBytes = ByteUtils.fromHexString(yHex);
        return createEcPublicKey(xBytes, yBytes, curve, domainParameters);
    }

    public static ECPublicKeyParameters createEcPublicKey(byte[] xBytes, byte[] yBytes, ECCurve curve, ECDomainParameters domainParameters) {
        byte[] encodedPubKey = new byte[1 + xBytes.length + yBytes.length];
        encodedPubKey[0] = 4;
        System.arraycopy(xBytes, 0, encodedPubKey, 1, xBytes.length);
        System.arraycopy(yBytes, 0, encodedPubKey, 1 + xBytes.length, yBytes.length);
        return new ECPublicKeyParameters(curve.decodePoint(encodedPubKey), domainParameters);
    }

    public static byte[] convertEcPriKeyToPkcs8Der(ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) {
        ECDomainParameters domainParams = priKey.getParameters();
        ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(), domainParams.getN(), domainParams.getH());
        BCECPublicKey publicKey = null;
        if (pubKey != null) {
            publicKey = new BCECPublicKey("EC", pubKey, spec, BouncyCastleProvider.CONFIGURATION);
        }

        BCECPrivateKey privateKey = new BCECPrivateKey("EC", priKey, publicKey, spec, BouncyCastleProvider.CONFIGURATION);
        return privateKey.getEncoded();
    }

    public static String convertPkcs8DerEcPriKeyToPem(byte[] encodedKey) throws IOException {
        return convertDerEcDataToPem("EC PRIVATE KEY", encodedKey);
    }

    public static byte[] convertPemToPkcs8DerEcPriKey(String pemString) throws IOException {
        return convertPemToDerEcData(pemString);
    }

    public static byte[] convertEcPriKeyToPkcs1Der(ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) throws IOException {
        byte[] pkcs8Bytes = convertEcPriKeyToPkcs8Der(priKey, pubKey);
        PrivateKeyInfo pki = PrivateKeyInfo.getInstance(pkcs8Bytes);
        ASN1Encodable encodable = pki.parsePrivateKey();
        ASN1Primitive primitive = encodable.toASN1Primitive();
        return primitive.getEncoded();
    }

    public static byte[] convertEcPubKeyToX509Der(ECPublicKeyParameters pubKey) {
        ECDomainParameters domainParams = pubKey.getParameters();
        ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(), domainParams.getN(), domainParams.getH());
        BCECPublicKey publicKey = new BCECPublicKey("EC", pubKey, spec, BouncyCastleProvider.CONFIGURATION);
        return publicKey.getEncoded();
    }

    public static String convertX509DerEcPubKeyToPem(byte[] encodedKey) throws IOException {
        return convertDerEcDataToPem("PUBLIC KEY", encodedKey);
    }

    public static byte[] convertPemToX509DerEcPubKey(String pemString) throws IOException {
        return convertPemToDerEcData(pemString);
    }

    public static ECPrivateKeyParameters convertPkcs1DerToEcPriKey(byte[] encodedKey) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
        PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(encodedKey);
        KeyFactory kf = KeyFactory.getInstance("EC", "BC");
        BCECPrivateKey privateKey = (BCECPrivateKey)kf.generatePrivate(peks);
        ECParameterSpec ecParameterSpec = privateKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(), ecParameterSpec.getG(), ecParameterSpec.getN(), ecParameterSpec.getH());
        return new ECPrivateKeyParameters(privateKey.getD(), ecDomainParameters);
    }

    public static ECPublicKeyParameters convertX509DerToEcPrublicKey(byte[] encodedKey) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
        X509EncodedKeySpec xeks = new X509EncodedKeySpec(encodedKey);
        KeyFactory kf = KeyFactory.getInstance("EC", "BC");
        BCECPublicKey publicKey = (BCECPublicKey)kf.generatePublic(xeks);
        ECParameterSpec ecParameterSpec = publicKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(), ecParameterSpec.getG(), ecParameterSpec.getN(), ecParameterSpec.getH());
        return new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters);
    }

    private static String convertDerEcDataToPem(String type, byte[] encodedData) throws IOException {
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        PemWriter pWrt = new PemWriter(new OutputStreamWriter(bOut, StandardCharsets.UTF_8));
        Throwable var4 = null;

        try {
            PemObject pemObj = new PemObject(type, encodedData);
            pWrt.writeObject(pemObj);
        } catch (Throwable var13) {
            var4 = var13;
            throw var13;
        } finally {
            if (pWrt != null) {
                if (var4 != null) {
                    try {
                        pWrt.close();
                    } catch (Throwable var12) {
                        var4.addSuppressed(var12);
                    }
                } else {
                    pWrt.close();
                }
            }

        }

        return new String(bOut.toByteArray(), StandardCharsets.UTF_8);
    }

    private static byte[] convertPemToDerEcData(String pemString) throws IOException {
        ByteArrayInputStream bIn = new ByteArrayInputStream(pemString.getBytes(StandardCharsets.UTF_8));
        PemReader pRdr = new PemReader(new InputStreamReader(bIn, StandardCharsets.UTF_8));
        Throwable var3 = null;

        byte[] var5;
        try {
            PemObject pemObject = pRdr.readPemObject();
            var5 = pemObject.getContent();
        } catch (Throwable var14) {
            var3 = var14;
            throw var14;
        } finally {
            if (pRdr != null) {
                if (var3 != null) {
                    try {
                        pRdr.close();
                    } catch (Throwable var13) {
                        var3.addSuppressed(var13);
                    }
                } else {
                    pRdr.close();
                }
            }

        }

        return var5;
    }
}
